package com.dragon.modules.yuegou.client.wechat_mini.conn;

import com.dragon.modules.yuegou.client.wechat_mini.common.MiniException;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;

/**
 * 微信客户端的Apache HttpClient实现
 * 
 * @author liuwu
 * @date 2019年7月6日
 *
 */
public class ApacheMiniClient implements IMiniClient {

	public static final String BASE_URL = "https://api.weixin.qq.com";

	private static final String MSG_URL = "URL格式错误";

	protected final Logger logger = LoggerFactory.getLogger(this.getClass());

	protected static final String ACCESS_TOKEN = "access_token";

	protected final String baseUrl;

	protected final CloseableHttpClient httpClient;

	protected final ObjectMapper objectMapper;

	public ApacheMiniClient() {
		this(null, BASE_URL);
	}

	public ApacheMiniClient(@Nullable CloseableHttpClient httpClient) {
		this(httpClient, BASE_URL);
	}

	public ApacheMiniClient(@Nullable CloseableHttpClient httpClient, @Nullable String baseUrl) {
		super();
		this.baseUrl = baseUrl != null ? baseUrl : BASE_URL;
		this.httpClient = httpClient != null ? httpClient : createDefaultHttpClient();
		this.objectMapper = createDefaultMapper();
	}

	private static ObjectMapper createDefaultMapper() {
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
		objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
		objectMapper.configure(JsonGenerator.Feature.WRITE_NUMBERS_AS_STRINGS, true);
		objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true);
		return objectMapper;
	}

	private static CloseableHttpClient createDefaultHttpClient() {
		// 不能使用HttpClient的默认配置，永不超时是不合理的
		RequestConfig config = RequestConfig.custom().setConnectTimeout(5000).setSocketTimeout(5000).build();
		return HttpClients.custom().setDefaultRequestConfig(config).build();
	}

	@Override
	public Builder get(String path) {
		return new BuilderImpl().get(path);
	}

	@Override
	public Builder post(String path) {
		return new BuilderImpl().post(path);
	}

	private class BuilderImpl implements Builder {

		URIBuilder uri;

		String textBody;
		HttpEntity requestBody;

		/**
		 * 是否是post，默认是GET
		 */
		boolean post;

		@Override
		public Builder get(String path) {
			try {
				uri = new URIBuilder(baseUrl + path);
			} catch (URISyntaxException e) {
				throw new MiniException(MSG_URL, e);
			}
			this.post = false;
			return this;
		}

		@Override
		public Builder post(String path) {
			try {
				uri = new URIBuilder(baseUrl + path);
			} catch (URISyntaxException e) {
				throw new MiniException(MSG_URL, e);
			}
			this.post = true;
			return this;
		}

		@Override
		public Builder json(Object body) {
			try {
				textBody = objectMapper.writeValueAsString(body);
				this.requestBody = new StringEntity(textBody, ContentType.APPLICATION_JSON);
			} catch (JsonProcessingException e) {
				throw new MiniException("数据异常，接口调用失败", e);
			}
			return this;
		}

		@Override
		public Builder httpEntity(HttpEntity httpEntity) {
			this.requestBody = httpEntity;
			return this;
		}

		@Override
		public Builder parameter(String name, String value) {
			uri.setParameter(name, value);
			return this;
		}

		@Override
		public void execute(OutputStream output) {
			HttpUriRequest request = buildHttpUriRequest();
			logRequest(request);
			try (CloseableHttpResponse httpResponse = httpClient.execute(request)) {
				logResponse(httpResponse);
				HttpEntity entity = httpResponse.getEntity();
				if (entity != null) {
					if (entity.getContentType() != null && entity.getContentType().getValue().contains("text/plain")) {
						String text = EntityUtils.toString(entity, "UTF-8");
						throw new MiniException("期待响应字节流，但是返回了文本内容：" + text);
					} else {
						entity.writeTo(output);
					}
				} else {
					throw new MiniException("没有响应内容");
				}
			} catch (IOException e) {
				throw new MiniException(e);
			}
		}

		@Override
		public <T> T execute(Type resultType) {
			HttpUriRequest request = buildHttpUriRequest();
			logRequest(request);
			try (CloseableHttpResponse httpResponse = httpClient.execute(request)) {
				logResponse(httpResponse);
				HttpEntity entity = httpResponse.getEntity();
				if (entity != null) {
					String responseText = EntityUtils.toString(entity, StandardCharsets.UTF_8);
					logger.debug("[body     ]<< {}", responseText);
					return objectMapper.readValue(responseText, objectMapper.constructType(resultType));
				} else {
					throw new MiniException("没有响应内容");
				}
			} catch (JsonProcessingException e) {
				throw new MiniException("数据异常，接口调用失败", e);
			} catch (IOException e) {
				throw new MiniException("IO异常，接口调用失败", e);
			}
		}

		/**
		 * 构建Apache HTTP Request对象
		 * 
		 * @return Apache HTTP Request对象
		 */
		private HttpUriRequest buildHttpUriRequest() {
			try {
				if (post) {
					HttpPost request = new HttpPost(uri.build());
					if (requestBody != null) {
						request.setEntity(requestBody);
					}
					return request;
				} else {
					return new HttpGet(uri.build());
				}
			} catch (URISyntaxException e) {
				throw new MiniException(MSG_URL);
			}
		}

		private void logRequest(HttpUriRequest request) {
			logger.debug("[request  ]>> {} {}", request.getMethod(), request.getURI());
			for (Header header : request.getAllHeaders()) {
				logger.debug("[header   ]>> {} : {}", header.getName(), header.getValue());
			}
			if (textBody != null) {
				logger.debug("[body     ]>> {}", textBody);
			}
		}

		private void logResponse(CloseableHttpResponse response) {
			logger.debug("[response ]<< {} {}", response.getStatusLine().getStatusCode(),
					response.getStatusLine().getReasonPhrase());
			for (Header header : response.getAllHeaders()) {
				logger.debug("[header   ]<< {} : {}", header.getName(), header.getValue());
			}
		}

	}

}
